package org.hikari.qqbot.xiuxian.task;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hikari.qqbot.xiuxian.command.*;
import org.hikari.qqbot.xiuxian.service.bo.SystemSetting;
import org.hikari.qqbot.xiuxian.service.bo.UserSetting;
import org.hikari.qqbot.xiuxian.util.TimedCache;

import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.LockSupport;
import java.util.stream.Collectors;

/**
 * created at 2024-12-19 00:38:22
 *
 * @author yinchao
 * @version 1.0
 **/
@Slf4j
@Getter
public class UserTaskRuntime {

    /**
     * 系统配置
     */
    private final SystemSetting systemSetting;
    /**
     * 用户配置
     */
    private final UserSetting userSetting;
    /**
     * 命令执行器
     */
    private final CommandRunner runner;
    /**
     * 任务列表
     */
    private final Collection<ITask> tasks;
    /**
     * 执行数据
     */
    private final RuntimeMeta meta;

    public UserTaskRuntime(SystemSetting systemSetting,
                           UserSetting userSetting,
                           CommandRunner runner,
                           Collection<ITask> tasks) {
        this.systemSetting = systemSetting;
        this.userSetting = userSetting;
        this.runner = runner;
        this.tasks = tasks;
        this.meta = new RuntimeMeta();
    }

    public void sendTaskCommand(String task, ICommand command) {
        sendTaskCommand(task, command, null);
    }

    public void sendTaskCommand(String task, ICommand command, ICommandSendCallback callback) {
        ExecutableCommand cmd = new ExecutableCommand(
                task,
                command.getCommand(),
                userSetting.getUserId(),
                command.getUserId() == null ? systemSetting.getXiuXianBotId() : command.getUserId(),
                command.getGroupId(),
                command.getSendTime(),
                callback
        );
        sendTaskCommand(cmd);
    }

    public void sendTaskCommand(ExecutableCommand cmd) {
        ICommandSendCallback statisticsCallback = new RuntimeDataCallback(meta);
        if (cmd.getCallback() == null) {
            cmd.setCallback(statisticsCallback);
        } else {
            cmd.setCallback(cmd.getCallback().then(statisticsCallback));
        }
        runner.sendCommand(cmd);
    }

    public void checkTaskHealth() {
        Collection<String> firstCheckTasks = this.noCommandWaitingTasks();
        // 如果存在异常任务，那么过指定窗口时间再检查一遍
        if (!firstCheckTasks.isEmpty()) {
            LockSupport.parkNanos((long) systemSetting.getTaskHealthCheckWindowSeconds() * 1000000000);

            Collection<String> secondCheckTasks = this.noCommandWaitingTasks();
            // 第一次检验结果在第二次中仍然存在，那么认为当前任务存在异常
            Collection<String> badTasks = firstCheckTasks.stream()
                    .filter(secondCheckTasks::contains)
                    .collect(Collectors.toSet());
            log.info("当前用户({}) 检测出{}条异常任务, 任务列表: {}", userSetting.getUserId(), badTasks.size(), badTasks);
            for (ITask task : tasks) {
                if (badTasks.contains(task.getId())) {
                    ICommand command = null;
                    try {
                        command = task.initCommand(userSetting);
                        if (command != null) {
                            sendTaskCommand(task.getId(), command);
                        }
                    } catch (Throwable e) {
                        log.error("[{} - {}] 任务尝试恢复失败! 恢复命令: {}", userSetting.getUserId(), task.getId(), command == null ? null : command.getCommand(), e);
                    }
                }
            }
        }
    }

    /**
     * 获取命令要发送的任务
     *
     * @return 任务列表
     */
    public Collection<String> noCommandWaitingTasks() {
        Set<String> taskIds = tasks.stream().map(ITask::getId).collect(Collectors.toSet());
        runner.searchCommand(cmd -> true)
                .forEach(cmd -> taskIds.remove(cmd.getTaskId()));
        return taskIds;
    }

    @Getter
    public static class RuntimeMeta {

        // 上次执行的命令
        private volatile CommandExecutionInfo lastCommandInfo;
        // 每一个任务的最后一次命令
        private final Map<String, CommandExecutionInfo> taskCommands = new ConcurrentHashMap<>();
        private final Map<String, Object> taskData = new ConcurrentHashMap<>();

        private void updateCommand(CommandExecutionInfo command) {
            this.lastCommandInfo = command;
            this.taskCommands.put(command.getTaskId(), command);
        }

        public CommandExecutionInfo getTaskLastCommandInfo(String task) {
            return taskCommands.get(task);
        }

        @SuppressWarnings("unchecked")
        public <T> T get(String key) {
            Object v = taskData.get(key);
            if (v == null) {
                return null;
            }
            return (T) v;
        }

        public void set(String key, Object data) {
            taskData.put(key, data);
        }

        public void remove(String key) {
            taskData.remove(key);
        }

        @SuppressWarnings("unchecked")
        public <T> TimedCache<T> getTimed(String key) {
            Object v = get(key);
            if (v == null) {
                return TimedCache.empty();
            }
            return (TimedCache<T>) v;
        }

        public void setTimed(String key, Object value, Date expireTime) {
            taskData.put(key, new TimedCache<>(value, expireTime));
        }

        public void setTimed(String key, Object value, long duration) {
            taskData.put(key, new TimedCache<>(value, duration));
        }

    }

    @Slf4j
    @RequiredArgsConstructor
    public static class RuntimeDataCallback implements ICommandSendCallback {

        private final RuntimeMeta meta;

        @Override
        public void onSuccess(CommandExecutionInfo command) {
            meta.updateCommand(command);
        }

        @Override
        public void onError(CommandExecutionInfo command, Throwable e) {
            meta.updateCommand(command);
        }

    }

}
